/**
 * This file is part of Lineage II Client Data Parser.
 * 
 * Lineage II Client Data Parser is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Lineage II Client Data Parser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Lineage II Client Data Parser.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.l2emuproject;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;

import net.l2emuproject.io.FileManager;
import net.l2emuproject.io.SafelyPackedFile;
import net.l2emuproject.parser.SkillParser;
import net.l2emuproject.parser.base.DefaultParser;
import net.l2emuproject.redist.CustomLogger;
import net.l2emuproject.redist.UnsupportedException;

import javolution.text.TextBuilder;

/**
 * @author Savormix
 *
 */
public final class Launcher
{
	private static final CustomLogger	log			= CustomLogger.getLog();
	/** All parseable client files */
	public static final String[]		FILES		=
													{
			"actionname-e",
			"armorgrp",
			"castlename-e",
			"Chargrp",
			"ClassInfo-e",
			"commandname-e",
			"Creditgrp-e",
			"entereventgrp",
			"etcitemgrp",
			"EULA-e",
			"gametip-e",
			"hairaccessorylocgrp",
			"Hennagrp-e",
			"HuntingZone-e",
			"InstantZoneData-e",
			"itemname-e",
			"Logongrp",
			"mobskillanimgrp",
			"MusicInfo",
			"npcgrp",
			"npcname-e",
			"Obscene-e",
			"optiondata_client-e",
			"questname-e",
			"raiddata-e",
			"Recipe-c",
			"ServerName-e",
			"shortcutalias",
			"SymbolName-e",
			"sysstring-e",
			"systemmsg-e",
			"skillgrp",
			"skillname-e",
			"skillsoundgrp",
			"StaticObject-e",
			"transformdata",
			"variationeffectgrp-e",
			"weapongrp",
			"ZoneName-e"							};
	private static final String[]		ALL			=
													{
			"actionname-e",
			"castlename-e",
			"Chargrp",
			"ClassInfo-e",
			"commandname-e",
			"Creditgrp-e",
			"entereventgrp",
			"EULA-e",
			"gametip-e",
			"hairaccessorylocgrp",
			"Hennagrp-e",
			"HuntingZone-e",
			"InstantZoneData-e",
			"itemname-e",
			"Logongrp",
			"mobskillanimgrp",
			"MusicInfo",
			"npcname-e",
			"Obscene-e",
			"optiondata_client-e",
			"questname-e",
			"raiddata-e",
			"Recipe-c",
			"ServerName-e",
			"shortcutalias",
			"SymbolName-e",
			"sysstring-e",
			"systemmsg-e",
			"skillname-e",
			"skillsoundgrp",
			"StaticObject-e",
			"transformdata",
			"variationeffectgrp-e",
			"ZoneName-e"							};
	/** Chronicle names */
	public static final String[]		CHRONICLES	=
													{
			"Prelude [NOT SUPPORTED]",
			"Harbingers of War [NOT SUPPORTED]",
			"Age of Splendor [NOT SUPPORTED]",
			"Rise of Darkness [NOT SUPPORTED]",
			"Scions of Destiny [NOT SUPPORTED]",
			"Oath of Blood [NOT SUPPORTED]",
			"Interlude [NOT SUPPORTED]",
			"Kamael",
			"Hellbound",
			"Gracia P1",
			"Gracia P2",
			"Gracia Final",
			"Gracia Plus [NOT SUPPORTED]"			};

	private static final void showHelp()
	{
		log.info("Parameters:");
		log.info("[-v] verbose mode/debug output");
		log.info("[-altlook] alternative file lookup: will look for [filename].dat");
		log.info("[-memlim X] alternative memory limit (in MB), set 0 to disable " + "completely. Changing is HIGHLY NOT RECOMMENDED!");
		log.info("[-analysis X] analysis type:");
		log.info("0 - do not analyze [not recommended]");
		log.info("1 - use (only) deterministic analysis [default]");
		log.info("2 - use (only) experimental analysis");
		log.info("3 - use combined analysis");
		log.info("[-skilldesc] skill description inclusion type");
		log.info("0 - do not write description at all [reduces file size]");
		log.info("1 - write description only when it exists (for each level); " + "do not include higher level descriptions if they are exactly the same "
				+ "(until a different description is found) [default]");
		log.info("2 - write description for every skill level, ignore empty descriptions");
		log.info("3 - write a description for every skill level [not recommended]");
		log.info("[-skilldescnpc] add a NPC list after skill description if a skill level is used by any");
		log.info("[-noskilltype] skillType will be set to NOTDONE");
		log.info("");
		log.info("Chronicles:");
		for (int i = 0; i < CHRONICLES.length; i++)
			log.info(i + " - " + CHRONICLES[i]);
		log.info("");
		log.info("All files must be under /client_files/[chronicle]/");
		log.info("The files must be already decrypted as dec-[filename].dat");
		log.warn("Since l2encdec 2.92 generates only SafePackage files for ALL " + "chronicles, I am sorry to say, but files generated with older "
				+ "versions/other tools are no longer supported.");
		log.info("");
		showCommands();
	}

	private static void showFiles()
	{
		log.info("Parsed Files:");
		for (String name : FILES)
			log.info(name);
	}

	private static void showCommands()
	{
		log.info("Available commands:");
		log.info("all [chronicle ID] - Parse ALL files");
		log.info("[filename] [chronicle ID] - Parse a given file");
		log.info("help - display help");
		log.info("files - display a list of all parsed files");
		log.info("version - displays version information");
		log.info("quit - exit the application");
	}

	private static void showInfo()
	{
		log.info("Thank you for using " + VersionService.getAppName() + " " + VersionService.getVersion() + "!");
		log.info("If you wish to support further development of the application,");
		log.info("you should visit " + "http://www.l2emuproject.net");
		log.info("You can obtain the source code from the SVN repository:");
		log.info("http://subversion.assembla.com/svn/L2EmuProject/");
		log.info("The original author of this parser is Savormix, so a big thanks to him.");
		System.out.println();
	}

	private static void parseArgs(String[] args)
	{
		for (int i = 0; i < args.length; i++)
		{
			if (!args[i].startsWith("-"))
				continue;
			try
			{
				if (args[i].equals("-v"))
					CustomLogger.DEBUG_ENABLED = true;
				else if (args[i].equals("-altlook"))
					FileManager.ALTERNATIVE_LOOKUP = true;
				else if (args[i].equals("-dump"))
				{
					FileManager.SP_READ_OUTPUT = args[i + 1];
					FileManager.WRITE_TO_DUMP = true;
				}
				else if (args[i].equals("-memlim"))
				{
					FileManager.MAX_FILESIZE = Integer.parseInt(args[i + 1]);
					if (FileManager.MAX_FILESIZE == 0)
						FileManager.MAX_FILESIZE = Integer.MAX_VALUE;
					else if (FileManager.MAX_FILESIZE < 0)
						throw new NumberFormatException("Cannot be negative!");
				}
				else if (args[i].equals("-analysis"))
				{
					int type = Integer.parseInt(args[i + 1]);
					switch (type)
					{
						case 0:
							DefaultParser.ANALYZE_DETERMINISTIC = false;
							DefaultParser.ANALYZE_EXPERIMENTAL = false;
							break;
						case 1:
							DefaultParser.ANALYZE_DETERMINISTIC = true;
							DefaultParser.ANALYZE_EXPERIMENTAL = false;
							break;
						case 2:
							DefaultParser.ANALYZE_DETERMINISTIC = false;
							DefaultParser.ANALYZE_EXPERIMENTAL = true;
							break;
						case 3:
							DefaultParser.ANALYZE_DETERMINISTIC = true;
							DefaultParser.ANALYZE_EXPERIMENTAL = true;
							break;
						default:
							throw new NumberFormatException("Only 0-3 supported!");
					}
				}
				else if (args[i].equals("-skilldesc"))
				{
					int type = Integer.parseInt(args[i + 1]);
					switch (type)
					{
						case 0:
						case 1:
						case 2:
						case 3:
							SkillParser.DESCRIPTION = type;
							break;
						default:
							throw new NumberFormatException("Only 0-3 supported!");
					}
				}
				else if (args[i].equals("-skilldescnpc"))
					SkillParser.DESCRIPTION_NPC = true;
				else if (args[i].equals("-noskilltype"))
					SkillParser.NOTDONE = true;
			}
			catch (NumberFormatException nfe)
			{
				log.fatal("Malformed parameter (" + args[i] + ")!", nfe);
				System.exit(1);
			}
		}
	}

	private static boolean isParseRequest(String line)
	{
		for (String file : FILES)
			if (line.split(" ")[0].equalsIgnoreCase(file))
				return true;
		return false;
	}

	/**
	 * Application entry point/method
	 * @param args Arguments passed to the application
	 */
	public static void main(String[] args)
	{
		parseArgs(args);
		showInfo();
		showCommands();
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String line;
		try
		{
			while ((line = br.readLine()) != null)
			{
				boolean failed = false;
				line = line.trim().toLowerCase();
				if (line.equals("help"))
					showHelp();
				else if (line.equals("files"))
					showFiles();
				else if (line.equals("version"))
				{
					log.info(VersionService.getAppName());
					log.info(VersionService.getVersion());
				}
				else if (line.equals("quit"))
				{
					showInfo();
					System.exit(0);
				}
				else if (line.startsWith("all"))
				{
					TextBuilder sb = new TextBuilder();
					try
					{
						int cID = Integer.parseInt(line.substring(4));
						for (String file : ALL)
						{
							sb.delete(0, sb.length());
							sb.append(file);
							sb.append(' ');
							sb.append(cID);
							handleRequest(sb.toString());
						}
					}
					catch (StringIndexOutOfBoundsException ioob)
					{
						failed = true;
					}
					catch (ArrayIndexOutOfBoundsException aioob)
					{
						failed = true;
					}
				}
				else if (isParseRequest(line))
				{
					failed = !handleRequest(line);
				}
				else
					failed = true;
				if (failed)
					log.warn("Invalid command. Use \"help\" to obtain a list of " + "available options");
			}
		}
		catch (IOException e)
		{
			log.fatal("Cannot initialize input!", e);
			System.exit(1);
		}
	}

	//FIXME: make it look better
	private static final boolean handleRequest(String req) throws IOException
	{
		try
		{
			String[] param = req.split(" ");
			int cID = Integer.parseInt(param[1]);
			if (!FileManager.validateFile(param[0], cID))
			{
				log.debug(param[0] + " is not a valid decrypted client file [unknown error]!");
				return false;
			}
			DefaultParser dp = FileManager.getValidator(param[0]);
			SafelyPackedFile[] parsed = FileManager.getSPFile(cID, dp.getNeededFiles());
			for (int i = 0; i < parsed.length; i++)
			{
				if (!dp.validate(parsed[i]))
					throw new UnsupportedException("Invalid file format: " + parsed[i].getFileName());
				else
					parsed[i].resetPointer();
			}
			dp = dp.getClass().getConstructor(SafelyPackedFile[].class).newInstance(new Object[]
			{ parsed });
			dp.readClientData();
			dp.selfAdvancedAnalysis();
			dp.writeSimpleData(cID);
			dp.writeServerData(cID);
			dp.asyncWriteFinalizer();
		}
		catch (NumberFormatException nfe)
		{
			log.warn("Invalid chronicle ID specified!");
		}
		catch (FileNotFoundException fnfe)
		{
			log.error("Could not access client files!", fnfe);
		}
		catch (UnsupportedException iae)
		{
			log.error("Could not validate client files!", iae);
		}
		catch (StringIndexOutOfBoundsException ioob)
		{
			return false;
		}
		catch (ArrayIndexOutOfBoundsException aioob)
		{
			return false;
		}
		catch (IllegalAccessException iae)
		{
			log.error("A validator was obtained instead of a parser. Lame.");
		}
		catch (SecurityException e)
		{
			/* Will not happen (no security manager used) */
		}
		catch (InstantiationException e)
		{
			/* Will not happen (dp was already instantiated) */
		}
		catch (InvocationTargetException e)
		{
			log.fatal("Failed while creating a parser!", e);
		}
		catch (NoSuchMethodException e)
		{
			log.fatal("Application was not compiled correctly!", e);
		}
		catch (IllegalArgumentException e)
		{
			log.fatal("Application was not compiled correctly!", e);
		}
		return true;
	}
}
